package com.flex.util {
   import flash.utils.ByteArray;

   import mx.utils.StringUtil;

   /**
    * This class provides the utilities to operate on String's
    * that are not provided by Flex.
    */
   public class StringUtils {

      /**
       * Length of names in VIM api objects
       */
      public static const VIM_NAME_LENGTH:uint = 80;

      /**
       * Return value if the string is fine.
       */
      public static const VIM_NAME_OK:String =
         "vimNameValidationOk";

      /**
       * Return value if the string contains no unicode characters, but is longer then
       * the given number of bytes.
       */
      public static const VIM_NAME_NO_UNICODE_TOO_LONG:String =
         "vimNameValidationNoUnicodeTooLong";

      /**
       * Return value if the string contains one or more unicode characters and uses
       * more bytes in its representation then the max bytes length given.
       */
      public static const VIM_NAME_HAS_UNICODE_TOO_LONG:String =
         "vimNameValidationHasUnicodeTooLongRepresentation";

      /**
       * Helper constant representing the empty string.
       */
      public static const EMPTY:String = "";

      /**
       * Checks if a give string is null or an empty string.
       */
      public static function isNullorEmpty(string:String):Boolean {
         return ((string == null) || (string.length == 0));
      }

      /**
       * Returns the argument string if it is not empty, otherwise returns null.
       */
      public static function nullIfEmpty(str:String):String {
         if (str != null && str.length == 0) {
            return null;
         }
         return str;
      }

      /**
       * Checks if a string ends with a pattern.
       *
       * @example The following examples show how to use this method:
       *
       * <listing version="3.0">
       * var str:String = "Hello, World";
       * var result:Boolean;
       * result = StringUtils.endsWith(str, "World"); // true
       * result = StringUtils.endsWith(str, "world"); // false
       * result = StringUtils.endsWith(str, "something else"); // false
       * </listing>
       */
      public static function endsWith(str:String, pattern:String):Boolean {
         return (pattern == str.substring(str.length - pattern.length));
      }

      /**
       * Checks if a string starts with a pattern.
       *
       * @example The following examples show how to use this method:
       *
       * <listing version="3.0">
       * var str:String = "Hello, World";
       * var result:Boolean;
       * result = StringUtils.startsWith(str, "Hello"); // true
       * result = StringUtils.startsWith(str, "hello"); // false
       * result = StringUtils.startsWith(str, "something else"); // false
       * </listing>
       */
      public static function startsWith(str:String, pattern:String):Boolean {
         return (pattern == str.substring(0, pattern.length));
      }

      /**
       * Appends the second string after the first one with the provided separator.
       * If one of the strings is empty the result is equal to the other one.
       */
      public static function append(str1:String, str2:String, separator:String) : String {
         if (isNullorEmpty(str1)) {
            return str2;
         }
         if (isNullorEmpty(str2)) {
            return str1;
         }

         return str1 + separator + str2;
      }

      /**
       * Substitutes "substitution" tokens within the specified string
       * with the respective arguments passed in.
       *
       * @param str
       *      The string to make substitutions in.
       *
       * @param substitutions
       *      Additional parameters that can be substituted in the <code>str</code>
       * parameter at each 'substitution' location, where 'substitution' is an
       * from one of the array elements from 'substitutions'. Each subsitition is an
       * object with a properties 'token' and 'value' where 'token' is the string
       * to be replaced and 'value' is the replacement.
       *
       * @return
       *      New string with all of the substitution tokens replaced with the
       * respective arguments specified.
       */
      public static function customSubstitue( str:String, substitutions:Array ):String {
         if (isNullorEmpty( str )) {
            return '';
         }
         for each (var substitution:Object in substitutions) {
            str = str.replace(substitution["token"], substitution["value"]);
         }
         return str;
      }

      /**
       * Trims quotes once from the beginning and the end of the string, if found in both
       * places.
       *
       * @param quoteStr
       *    The quoting string to trim out of the given string.
       */
      public static function trimMatchingQuotesOnce( str:String,
                                                     quoteStr:String = '"' ):String {
         var index:int = str.indexOf( quoteStr );
         if (index != 0) {
            return str;
         }
         index = str.lastIndexOf( quoteStr );
         if (index != str.length - quoteStr.length) {
            return str;
         }
         var endIndex:int = str.length - quoteStr.length;
         var result:String = str.substring( quoteStr.length, endIndex );
         return result;
      }

      /**
       * Captalize the first letter of the string.
       * @param value
       *    The string that needs first letter captalized.
       */
      public static function capitalize(value:String):String {
         if (isNullorEmpty(value)) {
            return "";
         }
         var firstChar:String = value.substr(0, 1);
         firstChar = firstChar.toUpperCase();
         var otherPart:String = value.substring(1);
         value = firstChar + otherPart;
         return value;
      }

      /**
       * Same as <code>String.split</code> but trims the result's array elements.
       *
       * @param str
       *       String to split.
       * @param delimiter
       *       Delimiter used when splitting.
       * @param splitEmpty
       *       Whether to split an empty string. If this is <code>false</code> and
       *       <code>str</code> is <code>null</code> or empty, an empty array is returned.
       *       Default value is <code>true</code>.
       * @return
       *       A string array.
       */
      public static function splitAndTrim(str:String, delimiter:String,
                                          splitEmpty:Boolean=true):Array/*String*/ {
         if (!splitEmpty && isNullorEmpty(str)) {
            return [];
         }
         var result:Array = str.split(delimiter);
         var len:int = result.length;
         for (var i:int = 0; i < len; i++) {
            result[i] = StringUtil.trim(result[i]);
         }
         return result;
      }

      /* Converts a string to a map based on the specified separators.
      * First splits the string into entries based on the entrySeparator, then into key/value pairs
      * based on the keyValueSeparator.
      *
      * @param mapString
      *    The string which represents this map.
      * @param entrySeparator
      *    The string which separates entries in the map.
      * @param keyValueSeparator
      *    The string which separates a key/value pair.
      * @return
      *    The map which this string represents.
      */
      public static function stringToMap(mapString:String, entrySeparator:String, keyValueSeparator:String):Object {
         if (StringUtils.isNullorEmpty(mapString)) {
            return null;
         }
         //Split the string based on declarations
         var entries:Array = mapString.split(entrySeparator);
         var entry:Array;
         var key:String;
         var value:String;
         var map:Object = new Object();

         for each (var entryStr:String in entries) {
            entry = splitAndTrim(entryStr, keyValueSeparator);
            if (entry.length == 2) {
               key = entry[0];
               value = entry[1];
               if (!StringUtils.isNullorEmpty(key)) {
                  map[key] = value;
               }
            }
         }
         return map;
      }
      /**
       *  Converts an array to a string with each element separated by a delimiter.
       *
       * @param arr
       *    Array string objects
       *
       * @param delimiter
       *    Delimiter to put between every two elements in the resulting string
       *
       * @return
       *    delimiter-separated string
       */
      public static function convertArrayToString(
         arr:Array/*String*/,
         delimiter:String = ",\n"):String {
         var result:String = "";

         if (arr == null || arr.length == 0){
            return result;
         }

         var first:Boolean = true;

         for each (var str:String in arr) {
            if (first) {
               result = result + str;
               first = false;
            } else {
               result = result.concat(delimiter, str);
            }
         }

         return result;
      }

      /**
       * Validate VIM object name against the number of bytes it may use in its
       * representation.
       *
       * @param name
       *       The input name to be validated
       *
       * @param byteLength
       *       The max number of bytes the name may use in its representation
       *
       * @return
       *       <code>VIM_NAME_OK</code> if the name is valid.
       *       <code>VIM_NAME_NO_UNICODE_TOO_LONG</code> if the name contains only ASCII
       *       characters, but is too long. <code>VIM_NAME_HAS_UNICODE_TOO_LONG</code> if
       *       the name contains any unicode characters and uses more bytes then the
       *       byteLength.
       */
      public static function validateVimName(
         name:String,
         byteLength:uint=VIM_NAME_LENGTH):String {
         var barray:ByteArray = new ByteArray();
         barray.writeUTFBytes(name); // This is UFT-8 with variable sized characters.
         if (barray.length > byteLength) {
            if (barray.length == name.length) {
               return VIM_NAME_NO_UNICODE_TOO_LONG;
            } else {
               return VIM_NAME_HAS_UNICODE_TOO_LONG;
            }
         }
         return VIM_NAME_OK;
      }
   } // class
} // package
